package com.kingbook.core.generator;

import com.kingbook.common.enums.JavaType;
import com.kingbook.common.file.LocalFileManager;
import com.kingbook.common.utils.StringUtils;
import com.kingbook.core.exception.ParamsException;
import com.kingbook.core.utils.JDBCHelper;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.jdom2.Content;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.input.SAXBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.support.EncodedResource;

import javax.sql.DataSource;
import java.io.File;
import java.sql.*;
import java.util.List;
import java.util.Map;

import static com.kingbook.core.enums.PrimaryKeyType.SEQUENCE;

/**
 * 用于项目开始生成 sql.xml javabean service rest
 * *
 *
 * @Author Bucky
 * @Date 2019/1/2
 * @Version 1.0
 */
public class Generator {

    private static final Logger logger = LoggerFactory.getLogger(Generator.class);

    private static final String pattern = "removeFirstUnderline";

    private DataSource dataSource;

    private DatabaseMetaData metaData;

    private String catalog;

    private String pkGeneratorGlobal;

    private String objectNamePattern;

    private String sequenceName;

    private String schemaConfig;

    private String sqlFileTarget;

    private String entityTarget;

    private String serviceTarget;

    private String restTarget;

    private String restPackage;

    private String servicePackage;

    private String javaBeanPackage;

    private boolean overWrite;

    private AnnotationInfo annotationInfo = new AnnotationInfo();

    private Map<String, String> tableComments = Maps.newHashMap();


    private String xsdNs = "http://www.kingbook.com.cn/configuration";

    public Generator() {
    }

    public void run() throws Exception {
        Element rootElement;
        try {
            EncodedResource enc = new EncodedResource(new FileSystemResource(schemaConfig), "utf-8");
            Document document = new SAXBuilder().build(enc.getReader());
            rootElement = document.getRootElement();
            String ns = rootElement.getNamespace().getURI();
            if (xsdNs.equals(ns)) {
                logger.info("----> check xml namespace {}. passed", ns);
            } else {
                logger.error("----> check xml namespace {}. no passed", ns);
                return;
            }
        } catch (Exception e) {
            logger.error("----> schemaConfig can`t find :{}", schemaConfig, e);
            throw e;
        }

        List<TableBean> tables = parseConfiguration(rootElement.getContent());
        if (StringUtils.isNotEmpty(sqlFileTarget)) {
            new SqlConfigurationGenerator(sqlFileTarget, tables, annotationInfo, overWrite).gen();
        }
        if (StringUtils.isNotEmpty(entityTarget) && StringUtils.isNotEmpty(javaBeanPackage)) {
            new JavaEntityClassGenerator(entityTarget, javaBeanPackage, tables, annotationInfo, overWrite).gen();
        }
        if (StringUtils.isNotEmpty(serviceTarget) && StringUtils.isNotEmpty(servicePackage)) {
            new ServiceClassGenerator(serviceTarget, servicePackage, javaBeanPackage, tables, annotationInfo, overWrite).gen();
        }
        if (StringUtils.isNotEmpty(restTarget) && StringUtils.isNotEmpty(restPackage)) {
            new RestClassGenerator(restTarget, restPackage, servicePackage, tables, annotationInfo, overWrite).gen();
        }
    }


    private List<TableBean> parseConfiguration(List<Content> list) {
        List<TableBean> tables = Lists.newArrayList();
        Connection connection = null;
        try {
            TableBean bean;
            connection = dataSource.getConnection();
            metaData = connection.getMetaData();
            catalog = connection.getCatalog();
            String dbType = metaData.getDatabaseProductName();
            if ("mysql".equalsIgnoreCase(dbType)) {
                loadTableComments(connection);
            } else if ("oracle".equalsIgnoreCase(dbType)) {
                loadTableComments(connection);
            } else {
                throw new RuntimeException("不支持的数据库类型：{}" + dbType);
            }
            String en;
            Element element;
            for (Content content : list) {
                if (content instanceof Element) {
                    element = (Element) content;
                    en = element.getName();
                    if ("table".equals(en)) {
                        if ("mysql".equalsIgnoreCase(dbType)) {
                            tables.add(parseTableForMySql(element));
                        } else if ("oracle".equalsIgnoreCase(dbType)) {
                            tables.add(parseTableForOracle(connection, element));
                        }
                    } else if ("primaryKey".equals(en)) {
                        pkGeneratorGlobal = element.getAttributeValue("generator");
                        if (SEQUENCE.value.equals(pkGeneratorGlobal)) {
                            sequenceName = element.getAttributeValue("sequenceName");
                        }
                    } else if ("objectName".equals(en)) {
                        objectNamePattern = element.getAttributeValue("pattern");
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCHelper.cleanup(connection, null, null);
        }
        return tables;
    }

    private void loadTableComments(Connection connection) throws SQLException {
        String sql = "SELECT table_name, table_comment FROM information_schema.tables WHERE table_schema = '" + catalog + "' ";
        ResultSet resultSet = connection.prepareStatement(sql).executeQuery();
        while (resultSet.next()) {
            tableComments.put(resultSet.getString(1), resultSet.getString(2));
        }
    }


    private TableBean parseTableForMySql(Element element) throws Exception {
        TableBean bean = loadTableBeanFromXML(element);
        String tableName = bean.getTableName();
        if (StringUtils.isNotEmpty(tableName)) {
            ResultSet resultPk = null;
            ResultSet resultCol = null;
            try {
                resultPk = metaData.getPrimaryKeys(catalog, null, tableName);
                resultCol = metaData.getColumns(catalog, null, tableName, null);
                loadColumnsDatabase(bean, element, resultPk, resultCol);
            } catch (Exception e) {
                throw new RuntimeException("查询数据库信息错误...");
            } finally {
                JDBCHelper.cleanup(null, null, resultPk);
                JDBCHelper.cleanup(null, null, resultCol);
            }
        }
        return bean;
    }

    private TableBean parseTableForOracle(Connection connection, Element element) throws Exception {
        TableBean bean = loadTableBeanFromXML(element);
        String tableName = bean.getTableName();
        if (StringUtils.isNotEmpty(tableName)) {
            Statement statement = null;
            ResultSet resultPk = null;
            ResultSet resultCol = null;
            try {
                String userName = metaData.getUserName();
                String sql = "select col.table_name from user_constraints con,user_cons_columns col " +
                        "where con.constraint_name=col.constraint_name and con.constraint_type='P' and col.table_name='" + tableName + "' and col.owner = '" + userName + "' ";
                statement = connection.prepareStatement(sql);
                resultPk = statement.executeQuery(sql);
                resultCol = metaData.getColumns(catalog, userName, tableName.toUpperCase(), "%");
                loadColumnsDatabase(bean, element, resultPk, resultCol);
            } catch (Exception e) {
                throw new RuntimeException("查询数据库信息错误...");
            } finally {
                JDBCHelper.cleanup(null, statement, resultPk);
                JDBCHelper.cleanup(null, null, resultCol);
            }

        }
        return bean;
    }

    private TableBean loadTableBeanFromXML(Element element) throws SQLException {
        TableBean bean = new TableBean();
        String tableName = element.getAttributeValue("tableName");
        String objectName = element.getAttributeValue("objectName");
        String createService = element.getAttributeValue("createService", "true");
        String createRest = element.getAttributeValue("createRest", "true");
        String createJavaBean = element.getAttributeValue("createJavaBean", "true");
        String useCache = element.getAttributeValue("useCache", "false");
        String cacheMaxSize = element.getAttributeValue("cacheMaxSize");
        String cacheDuration = element.getAttributeValue("cacheDuration");
        if (StringUtils.isNotEmpty(tableName)) {
            bean.setTableName(tableName);
            if (StringUtils.isEmpty(objectName)) {
                if (pattern.equalsIgnoreCase(objectNamePattern)) {
                    bean.setObjectName(StringUtils.toCapitalizeCamelUpCase(tableName.substring(tableName.indexOf("_"))));
                } else {
                    bean.setObjectName(StringUtils.toCapitalizeCamelUpCase(tableName));
                }
            } else {
                bean.setObjectName(objectName);
            }
            bean.setCreateService(StringUtils.isNotEmpty(createService) && "true".equalsIgnoreCase(createService));
            bean.setCreateJavaBean(StringUtils.isNotEmpty(createJavaBean) && "true".equalsIgnoreCase(createJavaBean));
            bean.setCreateRest(StringUtils.isNotEmpty(createRest) && "true".equalsIgnoreCase(createRest));
            bean.getCacheConfig().setUseCache(StringUtils.isNotEmpty(useCache) && "true".equalsIgnoreCase(useCache));
            bean.getCacheConfig().setCacheMaxSize(StringUtils.isNotEmpty(cacheMaxSize) ? Integer.parseInt(cacheMaxSize) : 1024);
            bean.getCacheConfig().setCacheDuration(StringUtils.isNotEmpty(cacheDuration) ? Integer.parseInt(cacheDuration) : 300);
            bean.setComment(tableComments.get(tableName));
        }
        return bean;
    }

    private void loadColumnsDatabase(TableBean bean, Element element, ResultSet resultPk, ResultSet resultCol) throws SQLException {
        String column;
        String jdbcType;
        String javaType;
        String remark;
        boolean nullable;
        Map<String, PrimaryKey> primaryKeyMap = Maps.newLinkedHashMap();
        Map<String, ColumnBean> columnOverrides = Maps.newLinkedHashMap();

        Map<String, ColumnBean> columns = Maps.newLinkedHashMap();
        while (resultPk.next()) {
            primaryKeyMap.put(resultPk.getString("COLUMN_NAME").toLowerCase(), null);
        }
        for (Content content : element.getContent()) {
            if (content instanceof Element) {
                Element e = (Element) content;
                if ("primaryKey".equals(e.getName())) {
                    column = e.getAttributeValue("column").toLowerCase();
                    if (primaryKeyMap.containsKey(column)) {
                        primaryKeyMap.put(column, new PrimaryKey(column, e.getAttributeValue("generator"), e.getAttributeValue("sequenceName"), e.getAttributeValue("functionName")));
                    }
                }
                if ("columnOverride".equals(e.getName())) {
                    column = e.getAttributeValue("column");
                    javaType = e.getAttributeValue("javaType");
                    remark = e.getAttributeValue("remark");
                    if (StringUtils.isNotEmpty(column)) {
                        columnOverrides.put(column, new ColumnBean(column, JavaType.getInstance(javaType) , remark, true));
                    }
                }
            }
        }
        for (String key : primaryKeyMap.keySet()) {
            if (primaryKeyMap.get(key) == null && StringUtils.isNotEmpty(pkGeneratorGlobal)) {
                String sName = sequenceName;
                if (StringUtils.isNotEmpty(sName) && sName.contains("${tableName}")) {
                    sName = sName.replace("${tableName}", bean.getTableName());
                }
                primaryKeyMap.put(key, new PrimaryKey(key, pkGeneratorGlobal, sName, ""));
            }
            if (primaryKeyMap.get(key) == null) {
                throw new ParamsException("----> 缺少主键生成方式：" + key, logger);
            }
        }

        ColumnBean columnBean;
        while (resultCol.next()) {
            column = resultCol.getString("COLUMN_NAME").toLowerCase();
            jdbcType = resultCol.getString("TYPE_NAME");
            remark = resultCol.getString("REMARKS");
            nullable = resultCol.getBoolean("NULLABLE");
            int length = resultCol.getInt("COLUMN_SIZE");
            if (columnOverrides.containsKey(column)) {
                columnBean = columnOverrides.get(column);
                remark = StringUtils.isNotEmpty(columnBean.remark) ? columnBean.remark : remark;
                columnBean.setRemark(remark + getPrimaryKeyInfo(primaryKeyMap, column));
                columnBean.setNullable(nullable);
                columns.put(column, columnBean);
            } else {
                columns.put(column, new ColumnBean(column, JDBCHelper.toJavaType(jdbcType), remark + getPrimaryKeyInfo(primaryKeyMap, column), nullable));
            }
        }
        bean.setPrimaryKeys(primaryKeyMap);
        bean.setColumns(columns);
    }


    private String getPrimaryKeyInfo(Map<String, PrimaryKey> primaryKeyMap, String column) {
        if (primaryKeyMap.containsKey(column)) {
            PrimaryKey p = primaryKeyMap.get(column);
            return " (主键 " + p.getPrimaryKeyType().value + ")";
        }
        return "";
    }


    void writeTemplate(String file, String str) {
        if (LocalFileManager.isExisted(new File(file))) {
            if (overWrite) {
                LocalFileManager.delete(file);
                LocalFileManager.writeFile(file, str);
                logger.info("----> create: {} 已覆盖", file);
            } else {
                logger.info("----> create: {} 不覆盖", file);
            }
        } else {
            LocalFileManager.writeFile(file, str);
            logger.info("----> create: {} 已生成", file);
        }
    }


    String readTemplate(String tplName) {
        String path = this.getClass().getProtectionDomain().getCodeSource().getLocation().getPath() + this.getClass().getPackage().getName().replace(".", "/");
        String s = LocalFileManager.readFile(path + "/" + tplName, "\n");
        if (StringUtils.isEmpty(s)) {
            throw new ParamsException("----> can`t find " + tplName + " in [" + path + "]", logger);
        }
        return s;
    }

    public void setDataSourceAndSchema(DataSource dataSource, String schemaConfig, boolean overWrite) {
        this.dataSource = dataSource;
        this.schemaConfig = schemaConfig;
        this.overWrite = overWrite;
    }

    public void setSqlFilePath(String sqlFileTarget) {
        this.sqlFileTarget = sqlFileTarget;
    }


    public void setEntityPath(String entityTarget, String javaBeanPackage) {
        this.entityTarget = entityTarget;
        this.javaBeanPackage = javaBeanPackage;
    }

    public void setServicePath(String serviceTarget, String servicePackage) {
        this.serviceTarget = serviceTarget;
        this.servicePackage = servicePackage;
    }

    public void setRestPath(String restTarget, String restPackage) {
        this.restTarget = restTarget;
        this.restPackage = restPackage;
    }

    public void setClassAnnotation(String author, String time, String version) {
        annotationInfo.setAuthor(author);
        annotationInfo.setTime(time);
        annotationInfo.setVersion(version);
    }
    public void setOverWrite(boolean overWrite) {
        this.overWrite = overWrite;
    }
}

